with Ada.Strings; use Ada.Strings;
with Ada.Strings.Fixed;
package body MIDI.Traces is

   Hex_Chars : constant array(Uint8_t range <>) of Character := 
      "0123456789ABCDEF";

   -- Note_Names : constant array(Note_Range) of String(1..2) := (
   --    "C ", "C#", 
   --    "D ", "D#", 
   --    "E ", 
   --    "F ", "F#",
   --    "G ", "G#",
   --    "A ", "A#",
   --    "B "
   --    );

   Note_Names : constant array(Note_Range) of String(1..2) := (
      "C ", "C#", 
      "D ", "D#", 
      "E ", 
      "F ", "F#",
      "G ", "G#",
      "A ", "A#",
      "B "
      );

   function To_Hex (N : Uint8_t) return String
   is
   begin
      return Hex_Chars(N / 16) & Hex_Chars(N mod 16);
   end To_Hex;

   function Image (T : Trunk_Head_Type) return String
   is
   begin
      return T.Name & " <len =>" & T.Size'Img & ">";
   end Image;

   function Image_Note (N : Uint8_t) return String
   is
      octave : Uint8_t := N / 12 - 1;
   begin
      return Fixed.Trim(Note_Names(n mod 12), Right) & Fixed.Trim(octave'Img, Left);
   end Image_Note;

   function Image (E : Control_Events.Note_Off_Event) return String
   is
   begin
      return "(Note =>" & Image_Note(E.Note) & ", Velocity =>" & E.Velocity'Img & ")";
   end Image;

   function Image (E : Control_Events.Note_On_Event) return String
   is
   begin
      return "(Note => " & Image_Note(E.Note) & ", Velocity =>" & E.Velocity'Img & ")";
   end Image;

   function Image (E : Control_Events.Key_After_Touch_Event) return String
   is
   begin
      return "(Note => " & Image_Note(E.Note) & ", Pressure =>" & E.Pressure'Img & ")";
   end Image;

   function Image (E : Control_Events.Controller_Change_Event) return String
   is
   begin
      return "(Controller =>" & Get_Controller_Name(E.Controller) & ", Value =>" & E.Value'Img & ")";
   end Image;

   function Image (E : Control_Events.Program_Change_Event) return String
   is
   begin
      return "(New_Patch =>" & E.Patch'Img & ")";
   end Image;

   function Image (E : Control_Events.Channel_After_Touch_Event) return String
   is
   begin
      return "(Pressure =>" & E.Pressure'Img & ")";
   end Image;

   function Image (E : Control_Events.Pitch_Wheel_Change_Event) return String
   is
   begin
      return "(Value =>" & Control_Events.To_Pitch_Value(E)'Img & ")";
      -- return "(Value =>" & E.LSB'Img & E.MSB'IMG & ")";
   end Image;
   
   function Image (C : Control_Events.Control_Event) return String
   is
      use Control_Events;
   begin
      return "[" & C.Name'Img & "], Channel =>" & C.Channel'Img & ", Data => " & (
         case C.Name is
            when Note_Off            => Image(C.As_Note_Off),
            when Note_On             => Image(C.As_Note_On),
            when Key_After_Touch     => Image(C.As_Key_After_Touch),
            when Controller_Change   => Image(C.As_Controller_Change),
            when Program_Change      => Image(C.As_Program_Change),
            when Channel_After_Touch => Image(C.As_Channel_After_Touch),
            when Pitch_Wheel_Change  => Image(C.As_Pitch_Wheel_Change));
   end Image;

   function Get_Controller_Name (Controller : Uint8_t) return String
   is
      H : String := To_Hex(Controller);

      function "&" (S : String; X : Uint8_t) return String
      is
      begin
         return S & X'Img;
      end "&";
   begin
      case Controller is
         when 0  => return H & "[Bank Select]";
         when 1  => return H & "[Modulation]";
         when 2  => return H & "[Breath Controller]";
         when 4  => return H & "[Foot Controller]";
         when 5  => return H & "[Portamento Time]";
         when 6  => return H & "[Data Entry (MSB)]";
         when 7  => return H & "[Main Volume]";
         when 8  => return H & "[Balance]";
         when 10 => return H & "[Pan]";
         when 11 => return H & "[Expression Controller]";
         when 12 .. 13 => return H & "[Effect Control" & (Controller - 11) & "]";
         when 16 .. 19 => return H & "[General-Purpose Controllers" & (Controller - 15) & "]";
         when 32 .. 63 => return H & "[LSB for controllers" & (Controller - 32) & "]";
         when 64 => return H & "[Damper pedal (sustain)]";
         when 65 => return H & "[Portamento]";
         when 66 => return H & "[Sostenuto]";
         when 67 => return H & "[Soft Pedal]";
         when 68 => return H & "[Legato Footswitch]";
         when 69 => return H & "[Hold 2]";
         when 70 => return H & "[Sound Controller 1 (default: Timber Variation)]";
         when 71 => return H & "[Sound Controller 2 (default: Timber/Harmonic Content)]";
         when 72 => return H & "[Sound Controller 3 (default: Release Time)]";
         when 73 => return H & "[Sound Controller 4 (default: Attack Time)]";
         when 74 .. 79 => return H & "[Sound Controller" & (Controller - 68) & "]";
         when 80 .. 83 => return H & "[General-Purpose Controllers" & (Controller - 75) & "]";
         when 84 => return H & "[Portamento Control]";
         when 91 => return H & "[Effects 1 Depth (formerly External Effects Depth)]";
         when 92 => return H & "[Effects 2 Depth (formerly Tremolo Depth)]";
         when 93 => return H & "[Effects 3 Depth (formerly Chorus Depth)]";
         when 94 => return H & "[Effects 4 Depth (formerly Celeste Detune)]";
         when 95 => return H & "[Effects 5 Depth (formerly Phaser Depth)]";
         when 96 => return H & "[Data Increment]";
         when 97 => return H & "[Data Decrement]";
         when 98 => return H & "[Non-Registered Parameter Number (LSB)]";
         when 99 => return H & "[Non-Registered Parameter Number (MSB)]";
         when 100 => return H & "[Registered Parameter Number (LSB)]";
         when 101 => return H & "[Registered Parameter Number (MSB)]";
         when 121 .. 127 => return H & "[Mode Messages]";
         when others => return H;
      end case;
   end Get_Controller_Name;

   function Get_Meta_Name (Command : Uint8_t) return String
   is
      H : String := To_Hex(Command);
   begin
      case Command is
         when 0      => return H & "[Sequence Number]";
         when 1      => return H & "[Text Event]";
         when 2      => return H & "[Copyright Notice]";
         when 3      => return H & "[Track Name]";
         when 4      => return H & "[Instrument Name]";
         when 5      => return H & "[Lyrics]";
         when 6      => return H & "[Marker]";
         when 7      => return H & "[Cue Point]";
         when 8      => return H & "[Program Name]";
         when 9      => return H & "[Device Name]";
         when 16#20# => return H & "[MIDI Channel Prefix]";
         when 16#21# => return H & "[MIDI Port]";
         when 16#2F# => return H & "[End Of Track]";
         when 16#51# => return H & "[Set Tempo]";
         when 16#54# => return H & "[SMPTE Offset]";
         when 16#58# => return H & "[Time Signature]";
         when 16#59# => return H & "[Key Signature]";
         when others => return H;
      end case;
   end Get_Meta_Name;

   function Image (M : Meta_Events.Meta_Event_Head_Type) return String
   is
   begin
      return "Command => " & Get_Meta_Name(M.Command) & ", Size =>" & M.Size'Img;
   end Image;

   function Image (T : XMIDI.Timbre_Type) return String
   is
   begin
      return "(Patch =>" & T.Patch'Img & ", Bank =>" & T.Bank'Img & ")";
   end Image;


   function Image (M : Meta_Events.Meta_Key_Signature) return String
   is
   begin
      return "Key =>" & M.Key'Img & ", Scale =>" & M.Scale'Img;
   end Image;

   function Image (M : Meta_Events.Meta_Time_Signature) return String
   is
      n_d : String := M.Numerator'Img & "/" & 
      Fixed.Trim(Uint8_t'Image(2**Natural(M.Denominator)), Left);
   begin
      return n_d & ", Clocks_Between_Metronome_Clicks =>" & M.Clocks_Between_Metronome_Clicks'Img & 
      ", Notated_32nd_Notes_In_4nd_Note =>" & M.Notated_32nd_Notes_In_4nd_Note'Img;
   end Image;

   function Image (M : Meta_Events.Meta_SMPTE_Offset) return String
   is
      function Pad_Left(X : Uint8_t) return String
      is
         S : String := Fixed.Trim(X'Img, Left);
      begin
         if S'Length < 2 then
            return '0' & S;
         else
            return S;
         end if;
      end Pad_Left;
   begin
      return "hh:mm:ss => " & Pad_Left(M.Hours) & ":" & Pad_Left(M.Minutes) & 
      ":" & Pad_Left(M.Seconds) & ", fps/sfpf =>" & M.Frames_Per_Second'Img & 
      "/" & Fixed.Trim(M.Fractional_Frames_Per_Frame'Img, Left);
   end Image;

   function Image (M : Meta_Events.Meta_Event) return String
   is
      use Meta_Events;
   begin
      case M.Command is
         when Commands.Sequence_Number         => return ", Value =>" & M.As_Sequence_Number.Value'Img;
         when Commands.Text                    => return ", Value => " & M.As_Text.Value;
         when Commands.Copyright_Note          => return ", Value => " & M.As_Copyright_Note.Value;
         when Commands.Seqeuence_Or_Track_Name => return ", Value => " & M.As_Sequence_Or_Track_Name.Value;
         when Commands.Instrument_Name         => return ", Value => " & M.As_Instrument_Name.Value;
         when Commands.Lyric                   => return ", Value => " & M.As_Lyric.Value;
         when Commands.Marker                  => return ", Value => " & M.As_Marker.Value;
         when Commands.Cue_Point               => return ", Value => " & M.As_Cue_Point.Value;
         when Commands.Program_Name            => return ", Value => " & M.As_Program_Name.Value;
         when Commands.Device_Name             => return ", Value => " & M.As_Device_Name.Value;
         when Commands.Channel_Prefix          => return ", Value =>" & M.As_Channel_Prefix.Value'Img;
         when Commands.Port                    => return ", Value =>" & M.As_Port.Value'Img;
         when Commands.End_Of_Track            => return ", <>"; 
         when Commands.Tempo                   => return ", BPM =>" & To_BPM(M.As_Tempo)'Img;
         when Commands.SMPTE_Offset            => return ", Value => " & Image(M.As_SMPTE_Offset);
         when Commands.Time_Signature          => return ", Signature => " & Image(M.As_Time_Signature);
         when Commands.Key_Signature           => return ", Signature => " & Image(M.As_Key_Signature);
         when others                           => return ", <>";
      end case;
   end Image;

   function Image (M : Events.Midi_Event_Type) return String
   is
      function Get_Name (Token : Uint8_t) return String
      is
         use Events;
      begin
         case Token is
            when Control =>
               return "[" & Control_Events.Types'Val(Token/16 - 8)'Img & "], Channel =>" & Uint8_t'Image(Token mod 16);
            when Meta    =>
               return "[Meta], Command =>" & Get_Meta_Name(M.As_Meta_Head.Command) & ", Size =>" & M.As_Meta_Head.Size'Img;
            when SysEx_Normal => 
               return "[SysEx Normal]";
            when SysEx_Divided =>
               return "[SysEx Divided]";
            when Timing_Clock_Used =>
               return "[Timing clock used when synchronization is required]";
            when Start_Sequence =>
               return "[Start current sequence]";
            when Continue_Sequence =>
               return "[Continue a stopped sequence where left off]";
            when Stop_Sequence =>
               return "[Stop a sequence]";
            when others => 
               return "[Unknown]";
         end case;
      end Get_Name;

      Name : String := To_Hex(M.Token) & Get_Name(M.Token);
   begin
      case M.Token is
         when Events.Note_Off            => return Name & ", " & Image(M.As_Note_Off);
         when Events.Note_On             => return Name & ", " & Image(M.As_Note_On);
         when Events.Key_After_Touch     => return Name & ", " & Image(M.As_Key_After_Touch);
         when Events.Controller_Change   => return Name & ", " & Image(M.As_Controller_Change);
         when Events.Program_Change      => return Name & ", " & Image(M.As_Program_Change);
         when Events.Channel_After_Touch => return Name & ", " & Image(M.As_Channel_After_Touch);
         when Events.Pitch_Wheel_Change  => return Name & ", " & Image(M.As_Pitch_Wheel_Change);
         when others                     => return Name;
      end case;
   end Image;

end MIDI.Traces;
